// -*- mode:C; tab-width:2; indent-tabs-mode:nil;  -*-
// $Id: queue.c,v 1.12 2010/04/21 00:23:27 zahorjan Exp $

/* Implements queue abstract data type. */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>

#include "lockDef.h"
#include "queue.h"
#include "factory.h"

//typedef struct queue_link_data_t *queue_link_t;

/* Define link
struct queue_link_data_t {
  queue_element_t e;
  queue_link_t next;
};
*/

/*
struct queue_t {
  queue_link_t head;
};
*/

INSTANTIATETYPE(queue_link_t,  
                queue_element_t e; 
                queue_link_t next;)
INSTANTIATETYPE(queue_t, 
                queue_link_t head;
                pthread_mutex_t lock;)

CREATEMANAGER(queue_link_t)
CREATEMANAGER(queue_t)

queue_t
queue_create()
{
  queue_t q = queue_t_acquire();
  if (!q) {
    printf("Queue acquire failed!\n");
    exit(1);
  }
  q->head = NULL;
  pthread_mutex_init(&q->lock,NULL );
  //pthread_cond_init(&q->can_remove,NULL);
  return q;
}

/* Private */
static queue_link_t
queue_new_element(queue_element_t e)
{
  queue_link_t qlt = queue_link_t_acquire();
  if (!qlt) {
    printf("Queue element acquire failed!\n");
    exit(1);
  }
  qlt->e  = e;
  qlt->next = NULL;
  return qlt;
}


void
queue_append(queue_t q, queue_element_t e) 
{	
  pthread_mutex_lock(&q->lock);
  if (q->head == NULL) {
    q->head = queue_new_element(e);
    // queue is not empty; append to tail
  } else {
    queue_link_t cur = q->head;
    while (cur->next) {
      cur = cur->next;
    }
    cur->next = queue_new_element(e);
  }
  //CSpthread_cond_signal(&q->can_remove);
  pthread_mutex_unlock(&q->lock);
}


boolean_t
queue_remove(queue_t q, queue_element_t *e) 
{
  pthread_mutex_lock(&q->lock);
  int result;
  queue_link_t oldHead;
  if (queue_is_empty(q)){
    pthread_mutex_unlock(&q->lock);
    return FALSE;
  }
  //while(queue_is_empty(q)){
  //  pthread_cond_wait(&q->can_remove, &q->lock);//return FALSE;
  //}
  *e = q->head->e;  
  oldHead = q->head;
  // q->head ref should still == 1 (temp oldHead ptr, and original)
  // don't call ref if creating a new pointer and getting rid of old
  q->head = q->head->next;
  queue_link_t_release(oldHead, NULL);
  result = TRUE;
  pthread_mutex_unlock(&q->lock);
  //check if oldHead refCnt is really 0 (as it should be)
  return result;
}

boolean_t
queue_is_empty(queue_t q)
{
  return (q->head == NULL);
}


/* private */
static boolean_t
queue_count_one(queue_element_t e, queue_pfapply_closure_t cl)
{
  int *x = (int*)cl;
  *x = *x + 1;
  return TRUE;
}

int
queue_size(queue_t q)
{
  pthread_mutex_lock(&q->lock);
  int sz = 0;
  queue_apply(q, queue_count_one, &sz);
  pthread_mutex_unlock(&q->lock);
  return sz;
}

boolean_t  
queue_apply(queue_t q, queue_pfapply_t pf, queue_pfapply_closure_t cl) 
{
  assert(q != NULL && pf != NULL);
  
  if (queue_is_empty(q))
    return FALSE;

  queue_link_t cur = q->head;
  while (cur) {
    if (!pf(cur->e, cl))
      break; 
    cur = cur->next;
  }
  return TRUE;
}

void 
queue_destroy( queue_t q, queue_element_pfdestroy_t elementDestructor ){
  pthread_mutex_lock(&q->lock);
  if (q->ar_refCnt<=1){
    queue_link_t cur = q->head;
    queue_link_t old_cur = cur;
    while(cur) {
      elementDestructor(cur->e);
      old_cur = cur;
      cur = cur->next;
      q->head = cur;
      queue_link_t_release(old_cur, NULL);
    }
  }   
  pthread_mutex_unlock(&q->lock);
  queue_t_release(q,NULL);
}
